package com.google.p2pbench;

import android.net.wifi.WifiConfiguration;
import android.util.Log;

import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * Wrapper for the hidden Wifi API.
 */
public class WifiAdhocApi {
  private static final String TAG = "WifiAdhoc";

  // WARNING: WifiConfigStore is not protected from incomplete
  // LinkProperties. Sending LinkProperties with a link address but
  // without a DNS server would result in a NullPointerException in
  // WifiConfigStore.getIpConfiguration which will put the device in an
  // endless soft-reboot loop.
  // In practice, LinkProperties are useless.
  private static Object IpAssignmentSTATIC;
  private static Class<?> LinkProperties;
  private static Class<?> LinkAddress;
  private static Class<?> RouteInfo;

  static {
    try {
      Object[] IpAssignment = Class.forName(
          "android.net.wifi.WifiConfiguration$IpAssignment").getEnumConstants();
      for (Object ia : IpAssignment) {
        if (ia.toString() == "STATIC") {
          IpAssignmentSTATIC = ia;
          break;
        }
      }
      LinkProperties = Class.forName("android.net.LinkProperties");
      LinkAddress = Class.forName("android.net.LinkAddress");
      RouteInfo = Class.forName("android.net.RouteInfo");
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

  private static void setIpAssignment(WifiConfiguration config,
      Object ipAssignment) throws IllegalArgumentException,
      IllegalAccessException, NoSuchFieldException {
    WifiConfiguration.class.getDeclaredField("ipAssignment").set(config,
        ipAssignment);
  }

  private static Object newLinkAddress(InetAddress address, int prefix_length)
      throws IllegalArgumentException, InstantiationException,
      IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    return LinkAddress.getConstructor(InetAddress.class, int.class)
        .newInstance(address, prefix_length);
  }

  @SuppressWarnings("unused")
  private static Object newRouteInfo(Object destination, InetAddress gateway)
      throws IllegalArgumentException, InstantiationException,
      IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    return RouteInfo.getConstructor(LinkAddress, InetAddress.class)
        .newInstance(destination, gateway);
  }

  private static Object newRouteInfo(InetAddress gateway)
      throws IllegalArgumentException, InstantiationException,
      IllegalAccessException, InvocationTargetException, NoSuchMethodException {
    return RouteInfo.getConstructor(InetAddress.class)
        .newInstance(gateway);
  }

  private static Object newLinkProperties() throws IllegalArgumentException,
      InstantiationException, IllegalAccessException,
      InvocationTargetException, NoSuchMethodException {
    return LinkProperties.getConstructor().newInstance();
  }

  private static void addLinkAddress(Object linkProperties, Object linkAddress)
      throws IllegalArgumentException, IllegalAccessException,
      InvocationTargetException, NoSuchMethodException {
    LinkProperties.getDeclaredMethod("addLinkAddress", LinkAddress).invoke(
        linkProperties, linkAddress);
  }

  private static void addDns(Object linkProperties, InetAddress address)
      throws IllegalArgumentException, IllegalAccessException,
      InvocationTargetException, NoSuchMethodException {
    LinkProperties.getDeclaredMethod("addDns", InetAddress.class).invoke(
        linkProperties, address);
  }

  private static void addRoute(Object linkProperties, Object routeInfo)
      throws IllegalArgumentException, IllegalAccessException,
      InvocationTargetException, NoSuchMethodException {
    LinkProperties.getDeclaredMethod("addRoute", RouteInfo).invoke(
        linkProperties, routeInfo);
  }

  private static void setLinkProperties(WifiConfiguration config,
      Object linkProperties) throws IllegalArgumentException,
      IllegalAccessException, NoSuchFieldException {
    WifiConfiguration.class.getDeclaredField("linkProperties").set(config,
        linkProperties);
  }

  // Copied from android.net.NetworkUtils:
  public static InetAddress intToInetAddress(int hostAddress) {
    byte[] addressBytes = { (byte)(0xff & hostAddress),
                            (byte)(0xff & (hostAddress >> 8)),
                            (byte)(0xff & (hostAddress >> 16)),
                            (byte)(0xff & (hostAddress >> 24)) };

    try {
       return InetAddress.getByAddress(addressBytes);
    } catch (UnknownHostException e) {
       throw new AssertionError();
    }
  }

  // NEW API!
  private static void setAdhocMode(WifiConfiguration config)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException {
    try {
      // defaults to channel 1
      WifiConfiguration.class.getDeclaredField("mode").setInt(config, 1);
    } catch (NoSuchFieldException e) {
      WifiConfiguration.class.getDeclaredField("isIBSS").setBoolean(config, true);
      // associate to channel 1
      WifiConfiguration.class.getDeclaredField("frequency").setInt(config, 2412);
    }
  }

  public static boolean configure(WifiConfiguration config,
      InetAddress address,
      int prefix_length, InetAddress dnsAddress) {
    try {
      Object linkProperties = newLinkProperties();
      Object linkAddress = newLinkAddress(address, prefix_length);
      addLinkAddress(linkProperties, linkAddress);
      // FIXME: The following settings are useless workarounds:
      // Make yourself gateway so that the ARP check passes.
      addRoute(linkProperties, newRouteInfo(address));
      // Make yourself gateway for multicast:
      //InetAddress multicastPrefix = Inet4Address.getByAddress(new byte[]{ 224 - 256, 0, 0, 0 });
      //addRoute(linkProperties, newRouteInfo(newLinkAddress(multicastPrefix, 4), address));
      addDns(linkProperties, dnsAddress);  // Does not matter really.
      setIpAssignment(config, IpAssignmentSTATIC);
      setLinkProperties(config, linkProperties);
      setAdhocMode(config);
      return true;
    } catch (Exception e) {
      Log.e(TAG, "Failed to configure", e);
    }
    return false;
  }

  public static String convertToSSID(String unquotedSsid) {
    return "\"" + unquotedSsid + "\"";
  }
}
